package org.jpropelleralt.jview.table.impl;

import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

import org.jpropelleralt.box.Box;
import org.jpropelleralt.change.Change;
import org.jpropelleralt.jview.JView;
import org.jpropelleralt.jview.list.impl.ListSelectionIndexModel;
import org.jpropelleralt.jview.list.impl.ListSelectionIndicesModel;
import org.jpropelleralt.ledger.Ledger;
import org.jpropelleralt.ledger.list.RecordTransformer;
import org.jpropelleralt.ledger.list.impl.ListLedger;
import org.jpropelleralt.ledger.list.impl.RecordTransformerNode;
import org.jpropelleralt.list.ListBox;
import org.jpropelleralt.node.Node;
import org.jpropelleralt.plumbing.NoInstanceAvailableException;
import org.jpropelleralt.plumbing.Source;
import org.jpropelleralt.ref.Ref;
import org.jpropelleralt.ref.impl.RefDefault;
import org.jpropelleralt.set.SetBox;

/**
 * {@link JView} using a {@link JViewTable}
 */
public class TableView implements JView {

	private final JViewTable table;

	/**
	 * Create a {@link TableView}
	 * @param tableModel		The {@link TableModel} to use
	 * @param selectionIndex	The selected index as a ref, or null to just use
	 * 							standard selection with no {@link Ref} behind it.
	 * @param sorting			True to support sorting, false otherwise
	 */
	private TableView(TableModel tableModel, Ref<Integer> selectionIndex, boolean sorting) {
		
		//Make the table
		table = new JViewTable(tableModel);
		
		if (selectionIndex != null) {
			//Only allow the selection to be set when the JViewTable is NOT responding
			//to a model change. 
			//This is somewhat messy, but is necessary to wrest control of updating the selection
			//away from the JTable - we already update the selection ourself in a more intelligent
			//way, so we only want the selection changes that are NOT in response to a table model
			//change, but in response to a user selection action
			Source<Boolean> filter = new Source<Boolean>() {
				@Override
				public Boolean get() throws NoInstanceAvailableException {
					return !table.isRespondingToChange();
				}
			};
			
			//Make the selection model
			table.setSelectionModel(new ListSelectionIndexModel(selectionIndex, filter, table));
		}
		
//		if (columnLayout == null) {
//			table = new JTableImproved(tableModel);
//		} else {
//			final DefaultTableColumnModel cm = new DefaultTableColumnModel();
//			table = new JTableImproved(tableModel, cm);
//			columnUpdater = new ColumnUpdater(tableModel, cm, columnLayout);
//		}
		
		if (sorting) {
			TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(tableModel);
			table.setRowSorter(sorter);
		}
		
	}

	/**
	 * Create a {@link TableView} with multiple selection
	 * @param tableModel		The {@link TableModel} to use
	 * @param selection			The selected indices as a {@link Ref}, or null to just use
	 * 							standard selection with no {@link Ref} behind it.
	 * @param sorting			True to support sorting, false otherwise
	 * @param typeErasureIsNotGood	Here to help Java out.
	 */
	private TableView(TableModel tableModel, Ref<SetBox<Integer>> selection, boolean sorting, boolean typeErasureIsNotGood) {
		
		//Make the table
		table = new JViewTable(tableModel);
		
		//Only allow the selection to be set when the JViewTable is NOT responding
		//to a model change. 
		//This is somewhat messy, but is necessary to wrest control of updating the selection
		//away from the JTable - we already update the selection ourself in a more intelligent
		//way, so we only want the selection changes that are NOT in response to a table model
		//change, but in response to a user selection action
		Source<Boolean> filter = new Source<Boolean>() {
			@Override
			public Boolean get() throws NoInstanceAvailableException {
				return !table.isRespondingToChange();
			}
		};
		
		//Make the selection model
		table.setSelectionModel(new ListSelectionIndicesModel(selection, filter, table));
		
//		if (columnLayout == null) {
//			table = new JTableImproved(tableModel);
//		} else {
//			final DefaultTableColumnModel cm = new DefaultTableColumnModel();
//			table = new JTableImproved(tableModel, cm);
//			columnUpdater = new ColumnUpdater(tableModel, cm, columnLayout);
//		}
		
		if (sorting) {
			TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(tableModel);
			table.setRowSorter(sorter);
		}
		
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref}
	 * @param <T>				The type of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param recordTransformer	{@link RecordTransformer} to convert {@link ListBox} elements into {@link Ledger} records
	 * @param selectionIndex	The selection index in the view - will be synchronised with table selection
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T> TableView create(Ref<ListBox<T>> list, RecordTransformer<T> recordTransformer, Ref<Integer> selectionIndex, boolean sorting) {
		ListLedger<T> ledger = ListLedger.create(list, recordTransformer);
		RefDefault<ListLedger<T>> ledgerRef = RefDefault.create(ledger);
		return create(ledgerRef, selectionIndex, sorting);
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref}, with
	 * list elements displayed as rows directly.
	 * @param <T>				The type of list element
	 * @param valueClass		The class of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param selectionIndex	The selection index in the view - will be synchronised with table selection
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T> TableView create(Class<T> valueClass, Ref<ListBox<T>> list, Ref<Integer> selectionIndex, boolean sorting) {
		ListLedger<T> ledger = ListLedger.create(valueClass, list);
		RefDefault<ListLedger<T>> ledgerRef = RefDefault.create(ledger);
		TableView view = create(ledgerRef, selectionIndex, sorting);
		view.removeHeader();
		return view;
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link Ledger} in a {@link Ref}
	 * @param ledger			The {@link Ref} to the {@link Ledger} to display
	 * @param selectionIndex	The selection index in the view - will be synchronised with table selection
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static TableView create(Ref<? extends Ledger> ledger, Ref<Integer> selectionIndex, boolean sorting) {
		LedgerTableModel model = LedgerTableModel.create(ledger);
		return new TableView(model, selectionIndex, sorting);
	}
	
	
	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref}
	 * @param <T>				The type of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param recordTransformer	{@link RecordTransformer} to convert {@link ListBox} elements into {@link Ledger} records
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T> TableView create(Ref<ListBox<T>> list, RecordTransformer<T> recordTransformer, boolean sorting) {
		return create(list, recordTransformer, null, sorting);
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref}, with
	 * list elements displayed as rows directly.
	 * @param <T>				The type of list element
	 * @param valueClass		The class of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T> TableView create(Class<T> valueClass, Ref<ListBox<T>> list, boolean sorting) {
		return create(valueClass, list, null, sorting);
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link Ledger} in a {@link Ref}
	 * @param ledger			The {@link Ref} to the {@link Ledger} to display
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static TableView create(Ref<? extends Ledger> ledger, boolean sorting) {
		return create(ledger, null, sorting);
	}
	

	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref}
	 * @param <T>				The type of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param recordTransformer	{@link RecordTransformer} to convert {@link ListBox} elements into {@link Ledger} records
	 * @param selection			The selected indices as a ref, or null to just use
	 * 							standard selection with no {@link Ref} behind it.
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T> TableView createMulti(Ref<ListBox<T>> list, RecordTransformer<T> recordTransformer, Ref<SetBox<Integer>> selection, boolean sorting) {
		ListLedger<T> ledger = ListLedger.create(list, recordTransformer);
		RefDefault<ListLedger<T>> ledgerRef = RefDefault.create(ledger);
		return createMulti(ledgerRef, selection, sorting);
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref},
	 * using a {@link RecordTransformerNode}
	 * @param <T>				The type of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param example			The example to use to make a {@link RecordTransformerNode}
	 * @param selection			The selected indices as a ref, or null to just use
	 * 							standard selection with no {@link Ref} behind it.
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T extends Node> TableView createMulti(Ref<ListBox<T>> list, T example, Ref<SetBox<Integer>> selection, boolean sorting) {
		RecordTransformerNode<T> recordTransformer = RecordTransformerNode.create(example);
		ListLedger<T> ledger = ListLedger.create(list, recordTransformer);
		RefDefault<ListLedger<T>> ledgerRef = RefDefault.create(ledger);
		return createMulti(ledgerRef, selection, sorting);
	}
	
	
	/**
	 * Create a new {@link TableView} displaying a {@link ListBox} in a {@link Ref}, with
	 * list elements displayed as rows directly.
	 * @param <T>				The type of list element
	 * @param valueClass		The class of list element
	 * @param list				The {@link Ref} to the {@link ListBox} to display
	 * @param selection			The selected indices as a ref, or null to just use
	 * 							standard selection with no {@link Ref} behind it.
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static <T> TableView createMulti(Class<T> valueClass, Ref<ListBox<T>> list, Ref<SetBox<Integer>> selection, boolean sorting) {
		ListLedger<T> ledger = ListLedger.create(valueClass, list);
		RefDefault<ListLedger<T>> ledgerRef = RefDefault.create(ledger);
		TableView view = createMulti(ledgerRef, selection, sorting);
		view.removeHeader();
		return view;
	}
	
	/**
	 * Create a new {@link TableView} displaying a {@link Ledger} in a {@link Ref}
	 * @param ledger			The {@link Ref} to the {@link Ledger} to display
	 * @param selection			The selected indices as a ref, or null to just use
	 * 							standard selection with no {@link Ref} behind it.
	 * @param sorting			True to support sorting of table, false otherwise.
	 * @return					A new {@link TableView}
	 */
	public static TableView createMulti(Ref<? extends Ledger> ledger, Ref<SetBox<Integer>> selection, boolean sorting) {
		LedgerTableModel model = LedgerTableModel.create(ledger);
		return new TableView(model, selection, sorting, true);
	}
	
	@Override
	public Format format() {
		return Format.LARGE;
	}

	@Override
	public JComponent getComponent() {
		return table;
	}

	@Override
	public boolean selfNaming() {
		return true;
	}

	@Override
	public void changes(Map<Box, Change> changes) {
		//Nothing to do, our table model will cause
		//us to update
	}

	@Override
	public void update() {
		//Nothing to do, our table model will cause
		//us to update
	}
	

	/**
	 * Gets the default editor of the contained {@link JTable}
	 * for a given class. See {@link JTable#getDefaultEditor(Class)}
	 * @param columnClass The edited class
	 * @return The default editor
	 */
	public TableCellEditor getDefaultEditor(Class<?> columnClass) {
		return table.getDefaultEditor(columnClass);
	}

	/**
	 * Gets the default renderer of the contained {@link JTable}
	 * for a given class. See {@link JTable#getDefaultRenderer(Class)}
	 * @param columnClass The rendered class
	 * @return The default renderer
	 */
	public TableCellRenderer getDefaultRenderer(Class<?> columnClass) {
		return table.getDefaultRenderer(columnClass);
	}

	/**
	 * Sets the default editor of the contained {@link JTable}
	 * for a given class. See {@link JTable#setDefaultEditor(Class, TableCellEditor)}
	 * @param columnClass The edited class
	 * @param editor The default editor
	 */
	public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor) {
		table.setDefaultEditor(columnClass, editor);
	}

	/**
	 * Sets the default renderer of the contained {@link JTable}
	 * for a given class. See {@link JTable#setDefaultRenderer(Class, TableCellRenderer)}
	 * @param columnClass The rendered class
	 * @param renderer The default renderer
	 */
	public void setDefaultRenderer(Class<?> columnClass,
			TableCellRenderer renderer) {
		table.setDefaultRenderer(columnClass, renderer);
	}
	
    /**
     * Sets the height, in pixels, of all cells to <code>rowHeight</code>,
     * revalidates, and repaints.
     * The height of the cells will be equal to the row height minus
     * the row margin.
     *
     * @param   rowHeight                       new row height
     * @exception IllegalArgumentException      if <code>rowHeight</code> is
     *                                          less than 1
     */
    public void setRowHeight(int rowHeight) {
    	table.setRowHeight(rowHeight);
    }
	
    /**
     * Remove header from table display
     */
    public void removeHeader() {
    	table.setTableHeader(null);
    }


}
